<?php

namespace control;

use Throwable;

/**
 * 接口类
 */
trait interfaces
{
	public static $redis;
	public array  $header = [];
	public mixed  $post;
	public mixed  $get;
	public string $time;
	public string $date;
	public int    $code   = 0;
	public array  $resp   = [];
	
	public function __construct()
	{
		$this->run();          //常用参数
		$this->redisConnect(); //连接redis
		// 自动加载类
		spl_autoload_register([$this, 'loadClass']);
	}
	
	public function init(): void
	{
		$this->run();          //常用参数
		$this->redisConnect(); //连接redis
		// 自动加载类
		spl_autoload_register([$this, 'loadClass']);
	}
	
	// 最后输出
	public function __destruct()
	{
		if ($this->resp) {
			static::json($this->resp, $this->code);
		}
	}
	
	/**
	 * 依赖函数
	 */
	final public function run(): void
	{
		global $post, $get, $time, $date;
		$origin = $_SERVER['HTTP_ORIGIN'] ?? '';          //发起请求来源域名
		header('Access-Control-Allow-Origin:' . $origin); //.   //放行允许访问域名
		header('Access-Control-Allow-Methods: *');        //允许所有请求方式。post,get
		header("Access-Control-Max-Age:600");             //为请求头生成缓存 减少与访问次数
		//多个参数英文逗号隔开 依次是Auth：token, Role:角色, Sign:参数签名, platform:参数来源android or ios, device:设备id
		header('Access-Control-Allow-Headers: Auth, Role, Sign, platform, device');
		$this->header = $this->getallHeaders();
		$this->post   = $post;
		$this->get    = $get;
		$this->time   = $time;
		$this->date   = $date;
	}
	
	// 自动加载类
	public function loadClass($className)
	{
		$fileName = stream_resolve_include_path(__DIR__ . '/' . $className . '.php');
		if (is_file($fileName) === false) {
			if (str_contains($className, 'adIndexClass')) {
				$fileName = serverRoot . str_replace('adIndexClass', 'adIndex', $className) . '.php';
			} elseif (!str_contains($className, '.php')) {
				$fileName = serverRoot . $className . '.php';
			}
		}
		if (stristr(PHP_OS, 'LINUX')) {
			$fileName = str_replace('\\', '/', $fileName);
		}
		require_once $fileName;
	}
	
	final public function post($key): array|string|null
	{
		return isset($_POST[$key]) ? formSubArray($_POST[$key]) : null;
	}
	
	final public function get($key): array|string|null
	{
		return isset($_GET[$key]) ? formSubArray($_GET[$key]) : null;
	}
	
	
	/**
	 * reids 连接
	 */
	private function redisConnect(): void
	{
		self::$redis = new \Redis();
		try {
			self::$redis->connect('127.0.0.1');
		} catch (Throwable $th) {
			exit($th);
		}
	}
	
	/**
	 * 获取redis 参数
	 */
	public static function rGet($key)
	{
		return self::$redis->get(redisKey . $key);
	}
	
	/**
	 * 设置redis 参数
	 */
	public static function rSet($key, $value, $timeout = 3600): void
	{
		self::$redis->setex(redisKey . $key, $timeout, $value);
	}
	
	/**
	 * 获取redis 参数剩余生存时间
	 */
	public static function rTtl($key): int
	{
		return self::$redis->TTL(redisKey . $key);
	}
	
	
	/**
	 * 获取header 参数
	 */
	private function getallHeaders(): array
	{
		foreach ($_SERVER as $name => $value) {
			if (str_starts_with($name, 'HTTP_')) {
				$this->header[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
			}
		}
		return $this->header;
	}
	
	// curl post
	public static function curlPost($url, $params, $headers = [], $timeout = 5, $build = true)
	{
		return curlPost($url, $params, $headers, $timeout, $build);
	}
	
	public static function curlGet($url, $headers = null, $timeout = 5): bool|string
	{
		return curlGet($url, $headers, $timeout);
	}
	
	public static function request(array $data, bool $isJson = false)
	{
		return request($data, $isJson);
	}
	
	/**
	 * Json 输出函数
	 *
	 * @param array $data 输出的数组 如 ['warn'=>1, 'data'=>$data]
	 * @param int   $code 状态码
	 * @return string
	 */
	final public static function json(array $data, int $code = 0): string
	{
		$data['warn'] = $data['warn'] ?? '';
		if ($data) {
			if (isset($data['data']) && ($data['warn'] === null || $data['warn'] === '')) {
				$warn = 'success';
			} else {
				$warn = $data['warn'] ?? '暂无数据';
			}
			unset($data['warn']);
			$res = [
				'code' => $code,
				'data' => isset($data['data']) ? self::throwJsonArray($data['data']) : null,
				'warn' => $warn,
			];
		} else {
			$res = [
				'code' => $code,
				'data' => [
					'warn' => '参数错误',
				],
			];
		}
		exit(json_encode($res, 320));
	}
	
	// 格式化json
	final public static function throwJson($data): int|float|array|string
	{
		if (empty($data) && !is_numeric($data)) {
			if (is_array($data)) {
				return [];
			} else {
				return '';
			}
		} else {
			return $data;
		}
	}
	
	final public static function throwJsonArray($data): int|float|array|string
	{
		if (!is_array($data)) {
			$result = self::throwJson($data);
		} elseif (empty($data)) {
			$result = self::throwJson($data);
		} else {
			foreach ($data as $key => $value) {
				$result[$key] = is_array($value) ? self::throwJsonArray($value) : self::throwJson($value);
			}
		}
		return $result;
	}
	
	/**
	 * 实体还原，多用于前端接口
	 */
	final public static function htmlDecode($string, $border = false): string
	{
		$temp = html_entity_decode($string);
		$temp = filterXss($temp);
		if ($border === true) {
			$temp = '<div class="kuang">' . $temp . '</div>';
		}
		return $temp;
	}
	
	
	/**
	 * 判断数组是否为空数组且不含有空值
	 *
	 * @param $array
	 * @return bool
	 */
	public static function is_null_array($array): bool
	{
		if (!is_array($array) || empty($array) || count($array) != count(array_filter($array, function ($value) { return !static::isNull($value); }))) {
			return true;
		}
		return false;
	}
	
	/**
	 * 按条件统计表数量
	 *
	 * @param array $data
	 * @return int|float|string 结果
	 */
	final public static function total(array $data): int|float|string
	{
		return total($data);
	}
	
	
	/**
	 * 获取参数签名
	 *
	 * @param array $params
	 * @return string
	 */
	public static function getSign(array $params): string
	{
		ksort($params); //按照key升序排列
		$strTemp = '';
		foreach ($params as $key => $item) {
			$strTemp .= $key . '=' . $item . '&'; //整合新的参数数组
		}
		$signTemp = $strTemp . "secret=" . secret; //拼接secret
		return md5($signTemp);
	}
	
	
	/**
	 * 获取验证码
	 *
	 * @param int        $tel 手机号
	 * @param string|int $code
	 * @param int        $exp 验证码有效期,默认为5分钟
	 * @return array
	 */
	public function getVeriCode(int $tel, string|int $code = '', int $exp = 60 * 5): array
	{
		$telExp = static::rGet(md5($tel));
		if ($telExp === $tel) {
			$resp['warn'] = '短信发送频繁，请稍后再试';
		} else {
			$code    = $code ?: rand(100000, 999999);
			$content = sms([
				"tel"           => $tel,
				"TemplateCode"  => para("aliyunTemplateCode"),
				"TemplateParam" => [
					"code" => $code, // 模板参数
				],
			]);
			$res     = self::objectToArray($content);
			if ($res['Message'] == 'OK') {
				static::rSet($tel, $code, $exp);
				static::rSet(md5($tel), $tel, $exp); //缓存这个手机号 5分钟
				$resp['warn'] = 'success';
			} else {
				$resp['warn'] = $res['Message'];
			}
		}
		return $resp;
	}
	
	
	// 对象转数组
	final public static function objectToArray($obj): array
	{
		$arr  = [];
		$_arr = is_object($obj) ? get_object_vars($obj) : $obj;
		foreach ($_arr as $key => $val) {
			$val       = (is_array($val) || is_object($val)) ? self::objectToArray($val) : $val;
			$arr[$key] = $val;
		}
		return $arr;
	}
	
	// 生成一个20位的ID，用于特定场合
	final public static function getNo(): string
	{
		return date('Ymd') . mt_rand(100, 999) . substr(implode(null, array_map('ord', str_split(substr(uniqid(), mt_rand(0, 6), 13)))), mt_rand(0, 4), 6) . mt_rand(100, 999);
	}
	
	// 生成一个ID
	final public static function getId($prefix = "a"): string
	{
		return suiji($prefix);
	}
	
	// 格式化日期
	final public static function formatDate($date, $format = 'Y-m-d'): string
	{
		if (!$date) {
			return '---';
		} elseif (strtotime($date) < 0) {
			return '---';
		} else {
			return date($format, strtotime($date));
		}
	}
	
	/**
	 * 数字数组格式化
	 *
	 * @param array $data
	 * @return array
	 */
	final public static function formateNumberArray(array $data): array
	{
		foreach ($data as $key => $value) {
			if (is_numeric($value)) {
				$data[$key] = round($value, 3);
			}
		}
		return $data;
	}
	
	/**
	 * 格式化金额显示
	 */
	final public static function formateNumber(float $money, string $unit = null, bool $style = false): string
	{
		$len = 0;
		if ($unit) {
			// 小数位
			$decimals = 2;
			switch ($unit) {
				case '万':
					$decimals = 3;
					$money    = round($money / 10000, $decimals);
					break;
				case '千':
					$money = round($money / 1000, $decimals);
					break;
			}
			$temp = explode('.', $money);
			$len  = (count($temp) > 1) ? strlen(end($temp)) : 0;
		}
		if ($style === true) {
			$unit = '<small class="data-unit">' . $unit . '</small>';
		}
		return (($money > 1000) ? number_format($money, $len) : $money) . $unit;
	}
	
	/** 公共选选项设置 数据保留小数位处理（主要用于保存数据前 处理数据）
	 *
	 * @param mixed  $number 数字
	 * @param string $type
	 * @return float
	 */
	final public static function formattingFigure(mixed $number = 0, string $type = 'amount'): float
	{
		$number   = floatval($number);
		$decimals = 4;
		switch ($type) {
			case 'numPrecision':
			case 'pricePrecision':
				$decimals = self::$publicSettings[$type]['num'] ?? $decimals;
				break;
			case 'amount': //公共金额设置
				$decimals = 2;
				break;
			case 'rate': //公共税率设置
				$decimals = 4;
				break;
			default:
				$decimals = self::$publicSettings[$type] ?? $decimals;
		}
		return round($number, $decimals);
	}
	
	/** 公共选选项设置 数据展示是否舍零（用于展示，例如：1.2选项设置保留3位小数，舍零（1.2），不舍零（1.200））
	 *
	 * @param mixed  $number     数字
	 * @param string $type
	 * @param bool   $formatting 格式化数字
	 * @return string
	 */
	final public static function lostZero(mixed $number = 0, string $type = 'amount', bool $formatting = false): string
	{
		$number = floatval($number);
		$figure = strlen(substr(strrchr($number, "."), 1));  //本身小数位数
		switch ($type) {
			case 'numPrecision':
				$decimals = self::$publicSettings[$type]['num'] ?? 0;
				$lostZero = self::$publicSettings[$type]['numLostZero'] ?? '';
				$figure   = max($figure, $decimals);
				$number   = $lostZero ? $number : number_format($number, $figure, ".", "");
				break;
			case 'pricePrecision':
				$decimals = self::$publicSettings[$type]['num'] ?? 0;
				$lostZero = self::$publicSettings[$type]['priceLostZero'] ?? '';
				$figure   = max($figure, $decimals);
				$number   = $lostZero ? $number : number_format($number, $figure, ".", "");
				break;
			default:
		}
		return $formatting === true ? number_format($number, $figure) : $number;
	}
	
	/** 公共选选项设置 输入框数字输入过滤（用于限制输入框只能输入所设置小数位的数字）
	 *
	 * @param string $type
	 * @return string
	 */
	final public static function digitalFiltering(string $type = 'amount'): string
	{
		$decimals = 4;
		switch ($type) {
			case 'numPrecision':
			case 'pricePrecision':
				$decimals = self::$publicSettings[$type]['num'] ?? $decimals;
				break;
			case 'amount': //公共金额设置
				$decimals = 2;
				break;
			case 'rate': //公共税率设置
				$decimals = 4;
				break;
			default:
				$decimals = self::$publicSettings[$type] ?? $decimals;
		}
		return " onkeyup=digitalFiltering(this," . $decimals . ") ";
	}
	
	/**
	 * 生成一个日期区间
	 *
	 * @param string $startDate 开始时间
	 * @param string $endDate   结束书简
	 * @param string $explode   分隔符
	 * @return array
	 */
	public static function dateRange(string $startDate, string $endDate, string $explode = '-'): array
	{
		$startInt = strtotime($startDate);
		$endInt   = strtotime($endDate);
		// 调换顺序
		if ($startInt > $endInt) {
			$tmp       = $startDate;
			$startDate = $endDate;
			$endDate   = $tmp;
		}
		//结束时间月份+1，如果是13则为新年的一月份
		$startArr   = explode($explode, $startDate);
		$startYear  = intval($startArr[0]);
		$startMonth = intval($startArr[1]);
		$endArr     = explode($explode, $endDate);
		$endYear    = intval($endArr[0]);
		$endMonth   = intval($endArr[1]);
		$data       = [];
		// 开始月份
		$data[]   = $startYear . $explode . str_pad($startMonth, 2, '0', STR_PAD_LEFT);
		$tmpMonth = $startMonth;
		$tmpYear  = $startYear;
		//如果起止不相等，一直循环
		while (!(($tmpMonth == $endMonth) && ($tmpYear == $endYear))) {
			$tmpMonth++;
			//超过十二月份，到新年的一月份
			if ($tmpMonth > 12) {
				$tmpMonth = 1;
				$tmpYear++;
			}
			$data[] = $tmpYear . $explode . str_pad($tmpMonth, 2, '0', STR_PAD_LEFT);
		}
		
		return $data;
	}
	
	/**
	 * 判断是否为空
	 */
	final public static function isNull($value): bool
	{
		if ($value === '' || $value === 'null' || is_null($value) || (is_array($value) && count($value) < 1)) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * 空函数
	 *
	 * @param      $value
	 * @param bool $isReturn
	 * @return string
	 */
	final public static function null($value, bool $isReturn = true): string
	{
		if (static::isNull($value)) {
			return $isReturn ? '---' : '';
		} elseif ($value == '0000-00-00' || $value == '0000-00-00 00:00:00') {
			return $isReturn ? '---' : '';
		} else {
			return $value;
		}
	}
	
	/**
	 * 格式化数字
	 *
	 * @param     $num
	 * @param int $precision
	 * @return int|string
	 */
	public static function numberSprintf($num, int $precision = 2): int|string
	{
		if ($num == 0) {
			return 0;
		}
		return sprintf("%01.{$precision}f", $num);
	}
	
	/**
	 * 加密解密
	 *
	 * @param string $data    加密明文
	 * @param bool   $decrypt 是否解密
	 * @return string
	 */
	final public static function encryptDecrypt(string $data, bool $decrypt = false): string
	{
		$method   = 'DES-ECB';
		$password = secret;
		$iv       = '';
		if ($decrypt) {
			return openssl_decrypt($data, $method, $password, 0, $iv);
		} else {
			return openssl_encrypt($data, $method, $password, 0, $iv);
		}
	}
	
	/**
	 * 生成订单编号
	 *
	 * @param array $data
	 * @return string
	 */
	final protected static function generateNo(array $data): string
	{
		$prefix  = $data['prefix'] ?? 'OR-' . date('Ymd') . '-';
		$table   = $data['table'] ?? 'user';
		$field   = $data['field'] ?? 'orderNo';
		$where   = $data['where'] ?? '';
		$default = $data['default'] ?? '0001';
		$length  = strlen($default); //填充位长度
		$res     = query($table, " `$field` LIKE '$prefix%' $where  ORDER BY $field DESC ");
		if (!$res[$field]) {
			return $prefix . $default;
		} else {
			(int)$temp = str_replace($prefix, '', $res[$field]);
			$index = $data['index'] ?? 1;
			return $prefix . (str_pad((intval($temp) + $index), $length, '0', STR_PAD_LEFT));
		}
	}
	
	/**
	 * 根据数组生成ID
	 *
	 * @param array $array
	 * @return string
	 */
	final protected static function generateId(array $array): string
	{
		$id = '';
		foreach ($array as $value) {
			if ($value) {
				$id .= $value;
			}
		}
		return $id ? 'a' . substr(md5($id), 0, 15) : '';
	}
	
	/**
	 * 获取员工信息
	 *
	 * @param array|string $stid
	 * @param string       $field
	 * @return string
	 */
	public static function findStaff(array|string $stid, string $field = 'name'): string
	{
		$temp = '';
		if (!is_array($stid)) {
			if ($stid) {
				$staff = query('staff', " stid = '$stid' ");
				$temp  .= $staff[$field];
			}
		} else {
			foreach ($stid as $id) {
				$temp .= self::findStaff($id, $field) . ',';
			}
			$temp = rtrim($temp, ',');
		}
		return $temp;
	}
	
	/**
	 * 获取员工部门信息
	 *
	 * @param array|string $stid
	 * @param string       $field
	 * @return string
	 */
	public static function findStaffDepartment(array|string $stid, string $field = 'name'): string
	{
		$temp = '';
		if (!is_array($stid)) {
			if ($stid) {
				$info = query('department', " `id` = (SELECT `departmentId` FROM job WHERE id = (SELECT jobId FROM staff WHERE stid = '$stid' LIMIT 1) ) ");
				$temp .= $info[$field];
			}
		} else {
			foreach ($stid as $id) {
				$temp .= self::findStaffDepartment($id, $field) . '，';
			}
			$temp = rtrim($temp, '，');
		}
		return $temp;
	}
	
	/**
	 * 生成分组字段
	 *
	 * @param array $array field group
	 * @return array
	 */
	public static function resolveField(array $array): array
	{
		$filedArray = self::initFieldArray($array['field']);
		$groupArray = self::initFieldArray($array['group']);
		$field      = $group = '';
		foreach ($filedArray as $value) {
			// 判断是否含有as
			if (str_contains($value, ' as ')) {
				$temp  = explode(' as ', $value);
				$v     = self::replaceSqlAlias(end($temp));
				$field .= " any_value($temp[0]) as `$v`,";
				// 判断字段是否在 分组里
			} elseif (!in_array($value, $groupArray)) {
				$v     = self::replaceSqlAlias($value);
				$field .= " any_value($value) as `$v`,";
			} elseif (!str_contains($value, '.')) {
				$group .= " `$value`, ";
				$field .= " `$value`, ";
			} else {
				$group .= " $value, ";
				$field .= " $value, ";
			}
		}
		return [
			'field' => rtrim($field, ', '),
			'group' => " GROUP BY " . rtrim($group, ', '),
		];
	}
	
	/**
	 * 去除sql 别名
	 *
	 * @param string $string
	 * @return string
	 */
	public static function replaceSqlAlias(string $string): string
	{
		if (!str_contains($string, '.')) {
			return $string;
		} else {
			$temp = explode('.', $string);
			return end($temp);
		}
	}
	
	/**
	 * 初始化字段数组
	 *
	 * @param string $string
	 * @return array
	 */
	public static function initFieldArray(string $string): array
	{
		$temp = explode(',', $string);
		$res  = [];
		foreach ($temp as $value) {
			$value = str_replace(['`', ','], '', $value);
			$value = trim($value);
			if ($value) {
				$res[] = $value;
			}
		}
		return $res;
	}
	
	
	/**
	 * 确认函数弹框，主要用于二次验证
	 *
	 * @param array $params
	 * @return string
	 */
	final public function confirm(array $params): string
	{
		$params['hidden'] = $params['hidden'] ?? '';
		$table            = [];
		$data             = [
			'title'    => $params['title'] ? ((isset($params['word']) && $params['word'] ? $params['title'] : para($params['title'])) ?? '提示信息') : null,
			'class'    => $params['class'] ?? $this->get('cla'),
			'function' => $params['function'] ?? 'isDelete',          //默认删除
			'form'     => $params['form'] ?? 'listForm',              // 表单信息
			'power'    => $params['power'] ?? '',
			'pwd'      => $params['pwd'] ?? true,
			'append'   => $params['append'] ?? [],
			'hidden'   => $params['hidden'] ?? '',
		];
		
		$uri    = 'cla=' . $data['class'] . '&fun=' . $data['function'];
		$hidden = $params['hidden'] . hidden(['name' => 'power', 'value' => $data['power']]);
		// 提示语
		if ($data['title']) {
			$table = [
				[
					'提示信息：' => $data['title'],
				],
			];
		}
		// 是否需要密码
		if ($data['pwd'] === true) {
			array_push($table, [
				must . '登录密码：' => password([
					'name'  => 'password',
					'title' => '请输入用户密码',
					'class' => "text",
				]),
			]);
		}
		// 附加表单
		if (!empty($data['append']) && is_array($data['append'])) {
			// 单个直接用一维数组，多个用二维数组
			if (count($data['append']) == count($data['append'], 1)) {
				array_push($table, $data['append']);
			} else {
				foreach ($data['append'] as $arr) {
					array_push($table, $arr);
				}
			}
		}
		// 按钮
		$button = '<button class="button" type="button" onclick="subForm(\'' . $data['form'] . '\',root+\'control/adIndex.html?' . $uri . '\')">确认提交</button>';
		$html   = $this->layerEdit([
			'table'  => $table,
			'button' => $button,
			'hidden' => $hidden,
		]);
		// 高度
		$height = $params['height'] ?? (200 + (count($table) - 1) * 40);
		return $this->layer(["title" => "操作选中的记录", "height" => $height, "width" => isset($params['width']) && $params['width'] ? $params['width'] : 650, 'winFix' => true, 'winAuto' => true, 'maxHeight' => '800', "zindex" => isset($params['zindex']) && $params['zindex'] ? $params['zindex'] : 98, "html" => $html]);
	}
	
	// 数据库相关
	final public static function insert(array $data, $empty = true): string
	{
		// 去除为空值key
		if ($empty === false) {
			$data['field'] = self::filterNull($data['field']);
		}
		return insert($data);
	}
	
	/**
	 * 批量插入数组
	 *
	 * @param string $table 表面
	 * @param array  $array 数组 需第一个key 为子表id
	 * @param bool   $debug
	 * @return string
	 */
	final public static function insertArray(string $table, array $array, bool $debug = false): string
	{
		$sql = '';
		foreach ((array)current($array) as $key => $id) {
			$field = [];
			foreach ($array as $k => $v) {
				$field[$k] = (is_array($v) && !empty($v)) ? $v[$key] : $v;
			}
			$firstKey = array_key_first($array);
			$where    = " `$firstKey` = '$id' ";
			if (static::total(['table' => $table, 'where' => $where]) < 1) {
				$sql .= static::insert([
					'table' => $table,
					'field' => $field,
					'debug' => $debug,
				]);
			} else {
				unset($field['id'], $field['time']);
				$sql .= static::update([
					'table' => $table,
					'field' => $field,
					'where' => $where,
					'debug' => $debug,
				]);
			}
		}
		return $sql;
	}
	
	// 修改
	final public static function update(array $data, $empty = true): string
	{
		// 去除为空值key
		if ($empty === false) {
			$data['field'] = self::filterNull($data['field']);
		}
		return update($data);
	}
	
	// 过滤空值
	public static function filterNull(array $array): array
	{
		foreach ($array as $key => $value) {
			if ($value === '' || $value === null || strtolower($value) === 'null') {
				unset($array[$key]);
			}
		}
		return $array;
	}
	
	
	/**
	 * 去除空数组
	 *
	 * @param array $array
	 * @return array
	 */
	public static function filterArray(array $array): array
	{
		// 去除重复
		$result = array_unique($array);
		// 去除空
		return array_filter($result, function ($value) { return !static::isNull($value); });
	}
	
	
	/**
	 * 时间比较，用于生成SQL
	 *
	 * @param string $str
	 * @param string $type
	 * @param string $field
	 * @return string
	 */
	public static function timeComparison(string $str, string $type = 'date', string $field = 'time'): string
	{
		$sqlString = '';
		if ($str) {
			$data = explode('~', $str);
			if (is_array($data)) {
				$startStamp = strtotime(trim($data[0]));
				$endStamp   = strtotime(trim(end($data)));
				switch ($type) {
					case 'day':
					case 'date':
						$format    = 'Y-m-d';
						$formatSql = '%Y-%m-%d';
						break;
					case 'mon':
					case 'month':
						$format    = 'Y-m';
						$formatSql = '%Y-%m';
						break;
					case 'year':
						$format    = 'Y';
						$formatSql = '%Y';
						break;
					case 'time':
					case 'datetime':
					default:
						$format    = 'Y-m-d H:i:s';
						$formatSql = '%Y-%m-%d %H:%i:%s';
						break;
				}
				$sqlString = " AND date_format($field, '$formatSql') >= '" . date($format, $startStamp) . "'  AND  date_format($field, '$formatSql') <= '" . date($format, $endStamp) . "' ";
			}
		}
		return $sqlString;
	}
	
	/**
	 * 详情页表单
	 *
	 * @param array $array
	 * @return string
	 */
	public function detailForm(array $array): string
	{
		$table = $this->tableMany($array);
		if ($array['orderId']) {
			$table .= hidden(['name' => 'orderId', 'value' => $array['orderId']]);
		}
		return "<div class='purchaseMx' style='margin:5px auto'><form name='detailForm'>$table</form></div>";
	}
	
	
	/**
	 * 创建文件夹
	 *
	 * @param $url
	 */
	public function _mkdir($url): void
	{
		$path = serverRoot;
		$arr  = explode('/', $url);
		if ($arr) {
			foreach ($arr as $val) {
				$path .= $val . '/';
				if (!is_dir($path)) {
					mkdir($path);
				}
			}
		}
	}
	
	/**
	 * 获取文件后缀
	 *
	 * @param $fileName
	 * @return string
	 */
	public static function getExtension($fileName): string
	{
		if (!empty($fileName)) {
			return strtolower(substr(strrchr($fileName, '.'), 1));
		} else {
			return '';
		}
	}
	
	/**
	 * 保存上传的文件到服务器
	 *
	 * @param array  $file      文件数组
	 * @param string $saveDir   保存文件夹名称
	 * @param array  $limitType 上传文件类型限制
	 * @param float  $maxSize   上传最大文件限制
	 * @param string $fileName  指定文件名称
	 * @return array 返回保存路径和警告信息  code:1 成功 0 失败
	 */
	public function saveFile(array $file, string $saveDir, array $limitType = [], float $maxSize = 0, string $fileName = ''): array
	{
		$maxSize  = empty($maxSize) ? 1024 * 1024 * 50 : $maxSize;
		$name     = $file['name'];
		$tmp_name = $file['tmp_name'];
		$suffix   = self::getExtension($name);
		$size     = 0;
		$src      = '';
		if ($file['error'] != UPLOAD_ERR_OK) {
			$warn = $this->getFileErrorInfo($file['error']);
		} elseif ($file['size'] > $maxSize) {
			$warn = '上传文件大于最大上传大小(M)' . ($maxSize / 1024 / 1024);
		} elseif (!empty($limitType) && !in_array($suffix, $limitType)) {
			$warn = '你上传的文件不在上传类型范围中';
		} elseif (empty($saveDir)) {
			$warn = '缺少保存文件夹名称';
		} else {
			$id  = $fileName ?: suiji();
			$url = 'file/' . $saveDir . "/" . date("Ym");
			$this->_mkdir($url);
			$src  = $url . "/" . $id . "." . $suffix; //保存附件的根目录路径
			$size = filesize($tmp_name);              //文件体积
			if (move_uploaded_file($tmp_name, serverRoot . $src)) {
				$warn = '上传成功';
			} else {
				$warn = '上传失败';
			}
		}
		return array('code' => $warn == '上传成功' ? 1 : 0, 'warn' => $warn, 'size' => $size, 'src' => $src, 'suffix' => $suffix, 'fileName' => $name);
	}
	
	/**
	 * 文件下载
	 *
	 * @param string $filename
	 * @param string $path
	 */
	public function downloadFile(string $filename = '', string $path = ""): void
	{
		$ext      = static::getExtension($path);
		$filename = $filename . '.' . $ext;
		if (empty($filename)) {
			$filename = basename($path);
		}
		header('Content-Description: File Transfer');
		header('Content-Type: application/octet-stream');
		header('Content-Disposition: attachment; filename=' . $filename);
		header('Content-Transfer-Encoding: binary');
		header('Expires: 0');
		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
		header('Pragma: public');
		header('Content-Length: ' . filesize($path));
		ob_clean();
		flush();
		readfile($path);
		exit;
	}
	
	/**
	 * 获取上传文件错误信息
	 *
	 * @param $error
	 * @return string
	 */
	public function getFileErrorInfo($error): string
	{
		return match ($error) {
			UPLOAD_ERR_INI_SIZE  => "上传的文件超过了upload_max_filesize 选项限制的值",
			UPLOAD_ERR_FORM_SIZE => "上传文件的大小超过了HTML表单中MAX_FILE_SIZE 选项指定的值",
			UPLOAD_ERR_PARTIAL   => "文件只有部分被上传",
			UPLOAD_ERR_NO_FILE   => "没有文件被上传",
			5                    => "文件大小为0",
			default              => '不明原因',
		};
	}
	
	/**
	 * 获取格式化后的文件大小
	 *
	 * @param $size
	 * @return string
	 */
	public static function formatSize($size): string
	{
		$sizes = array(" Bytes", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB");
		if ($size == 0) {
			return '';
		} else {
			return (round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . $sizes[$i]);
		}
	}
	
	/** 获取压缩文件路径
	 *
	 * @param $data
	 * @param $dir
	 * @return array
	 */
	public function getFileAll($data, $dir)
	{
		$attach_where = isset($data['attach_where']) ? $data['attach_where'] : '';
		// 结果
		$result = find(['table' => 'share', 'where' => $data['where'] . $attach_where]);
		$temp   = [];
		if ($result['array']) {
			foreach ($result['array'] as $value) {
				if ($value['type'] == 'folder') {
					$temp = array_merge($temp, $this->getFileAll(['where' => " pid = '" . $value['id'] . "' ", 'attach_where' => $attach_where], $dir . $value['name'] . '/'));
				} else {
					$temp[] = array("file" => serverRoot . $value['path'], "name" => $dir . $value['name']);
				}
			}
		}
		return $temp;
	}
	
	/** 文件压缩
	 *
	 * @param        $files
	 * @param        $saveDir
	 * @param string $filename
	 * @return array|false
	 */
	public function fileZip($files, $saveDir, string $filename = '')
	{
		//下面是实例操作过程：
		$dfile = 'file/' . $saveDir . '/' . date("Ym") . '/';
		$this->_mkdir($dfile);
		$zip = new \ZipArchive;
		//----------------------
		$filename = ($filename ?: suiji()) . '.zip'; //下载的默认文件名
		$res      = $zip->open(serverRoot . $dfile . $filename, \ZipArchive::CREATE);
		if ($res === true) {
			foreach ($files as $array) {
				$zip->addFile($array['file'], $array['name']);
			}
			//----------------------
			$zip->close();
			$size = filesize(serverRoot . $dfile . $filename);
			return ['filename' => $filename, 'path' => $dfile . $filename, 'size' => $size, 'suffix' => 'zip'];
		}
		return false;
	}
	
	/**
	 * 开启事务
	 */
	public function startTrans(): void
	{
		$con = $GLOBALS['con'];
		$con->autocommit(false); //关闭自动提交功能
	}
	
	/**
	 *提交事务
	 */
	public function commitTrans(): void
	{
		$con = $GLOBALS['con'];
		$con->commit();         //提交数据
		$con->autocommit(true); //开启自动提交功能
	}
	
	/**
	 * 事务回滚
	 */
	public function rollback(): void
	{
		$con = $GLOBALS['con'];
		$con->rollback();       //回滚
		$con->autocommit(true); //开启自动提交功能
	}
	
	/**
	 * 查询多条数据
	 *
	 * @param $sql
	 * @return array
	 */
	final public static function sqlFind($sql): array
	{
		return sqlFind($sql);
	}
	
	/**
	 * 导出Excel
	 *
	 * @param array  $title
	 * @param array  $data
	 * @param string $fileName
	 * @param string $savePath
	 */
	public function excelOut(array $title, array $data, string $fileName = '', string $savePath = ''): void
	{
		if (empty($fileName)) {
			$fileName = suiji() . '.xlsx';
		}
		$temp = ini_get('upload_tmp_dir');
		if ($temp !== false && file_exists($temp)) {
			$tempDir = realpath($temp);
		} else {
			$tempDir = realpath(sys_get_temp_dir());
		}
		$config = [];
		if (PHP_OS == 'Linux') {
			$config = [
				'path' => $tempDir . '/',
			];
		} elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
			//windows环境
			$config = [
				'path' => $tempDir,
			];
		}
		$excel    = new \Vtiful\Kernel\Excel($config);
		$filePath = $excel->fileName($fileName, 'sheet1')->header($title)->data($data)->output();
		if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
			//windows环境
			$filePath = str_replace("\\", "/", $filePath);
			$filePath = iconv(ini_get('default_charset'), "GBK//IGNORE", $filePath);
		}
		if (!$savePath) {
			header("Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
			header('Content-Disposition: attachment;filename="' . urlencode($fileName) . '"');
			header('Cache-Control: max-age=0');
			header('Content-Length: ' . filesize($filePath));
			header('Content-Transfer-Encoding: binary');
			header('Cache-Control: must-revalidate');
			header('Pragma: public');
			$bool = copy($filePath, 'php://output');
		} else {
			$bool = copy($filePath, $savePath);
		}
		ob_clean();
		flush();
		if ($bool === false) {
			exit('导出失败');
		} else {
			unlink($filePath);
		}
	}
	
	
	/**
	 * 修改数组key的前缀
	 *
	 * @param array  $array
	 * @param string $prefix
	 * @param bool   $remove
	 * @return array
	 */
	public static function arrayKeyPrefix(array $array, string $prefix, bool $remove = false): array
	{
		$temp = [];
		foreach ($array as $key => $value) {
			if ($remove === false) {
				$temp[$prefix . $key] = $value;
			} else {
				$temp[ltrim($key, $prefix)] = $value;
			}
		}
		return $temp;
	}
	
	/**
	 * 返回两个日期相差天数
	 *
	 * @param $startTime
	 * @param $endTime
	 * @return float|bool
	 */
	public static function diffDays($startTime, $endTime): float|bool
	{
		if (static::isNull($startTime) || static::isNull($endTime)) {
			return false;
		}
		$startTime = strtotime($startTime);
		$endTime   = strtotime($endTime);
		
		if ($startTime > $endTime) {
			$temp      = $startTime;
			$startTime = $endTime;
			$endTime   = $temp;
		}
		return round(($endTime - $startTime) / 3600 / 24);
	}
	
	/**
	 * 二维数组排序
	 *
	 * @param array  $arrays     需要排序的数组
	 * @param string $sort_key   排序的字段
	 * @param int    $sort_order 升序或降序
	 * @param int    $sort_type  排序字段类型
	 * @return array|bool 排序后的数组
	 */
	public static function my_sort(array $arrays, string $sort_key, int $sort_order = SORT_ASC, int $sort_type = SORT_NUMERIC): array|bool
	{
		foreach ($arrays as $array) {
			if (is_array($array)) {
				$key_arrays[] = $array[$sort_key];
			} else {
				return false;
			}
		}
		array_multisort($key_arrays, $sort_order, $sort_type, $arrays);
		return $arrays;
	}
	
	/**
	 * 通过手机号码查找微信Userid
	 */
	public function getWechatUserIdByTel($tel)
	{
		$res = static::request([
			'method' => 'POST',
			'url'    => 'https://qyapi.weixin.qq.com/cgi-bin/user/getuserid?debug=1&access_token=' . $this->getCorpToken('oa', false),
			'params' => json_encode([
				'mobile' => $tel,
			], 320),
		], true);
		if ($res['errcode'] == 0) {
			return $res['userid'];
		} else {
			test("请求userId失败：" . $res['errmsg']);
			return null;
		}
	}
	
	/**
	 * 发送企业微信小程序消息
	 *
	 * @param array  $stids       员工ID
	 * @param string $title       标题 4-12个汉字
	 * @param array  $content     内容
	 * @param string $page        小程序跳转路径  //pages/index/index?userid=zhangsan&orderid=123123123
	 * @param string $description 描述  4-12个汉字
	 */
	public static function sendQYWechatMessage(array $stids, string $title, array $content, string $page = '', string $description = '')
	{
		// $token  = wechatToken('oaAccessToken');
		$token  = self::getCorpToken('oa');
		$conten = array(
			'appid'               => para('wxOaAppid'),
			'title'               => $title,
			'emphasis_first_item' => false,//是否放大第一个content_item
			'content_item'        => $content,
		);
		if ($page) {
			$conten['page'] = $page;
		}
		if ($description) {
			$conten['description'] = $description;
		}
		$data = array(
			'touser'             => self::getUserIds($stids),
			'msgtype'            => 'miniprogram_notice',
			'miniprogram_notice' => $conten,
		);
		if (empty($data['touser'])) {
			test('发送消息时未找到企业微信ID');
		} else {
			$result = self::curlPost("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" . $token, json_encode($data, 320), build: false);
			$res    = json_decode($result, true);
			if ($res['errcode'] != 0) {
				test($res['errmsg']);
			} else {
				return $res;
			}
		}
		return false;
	}
	
	
	/**
	 * 撤销企业微信消息(24小时内)
	 *
	 * @param string $msgid 发送的通知消息ID
	 * @return bool|false
	 */
	public static function revokeQyMessage(string $msgid): bool
	{
		$token  = self::getCorpToken('oa');
		$data   = array(
			'msgid' => $msgid,
		);
		$result = self::curlPost("https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=" . $token, json_encode($data, 320), build: false);
		$res    = json_decode($result, true);
		if ($res['errcode'] != 0) {
			test($res['errmsg']);
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * 根据员工ID获取企业微信userId
	 *
	 * @param $stids
	 * @return string
	 */
	public static function getUserIds($stids): string
	{
		$weChatUserIds = [];
		if (is_array($stids)) {
			foreach ($stids as $stid) {
				$user = query('user', "stid = '$stid'");
				if ($user['weChatUserId']) $weChatUserIds[] = $user['weChatUserId'];
			}
		} else {
			$user = query('user', "stid = '$stids'");
			if ($user['weChatUserId']) $weChatUserIds[] = $user['weChatUserId'];
		}
		return implode('|', $weChatUserIds);
	}
	
	/**
	 * 导入尝试把日期格式的字段转为日期、
	 * excel导入如果为日期格式，获取到的数据则是从1900-01-01至今的天数，所以用这个天数减去1970-01-01这中间70的天数再转时间戳，可以换取到php的日期
	 * 25569是70年的天数，其中有加闰年的  365*70+18+1
	 *
	 * @param $value
	 * @return mixed
	 */
	public static function importDateFormat($value): mixed
	{
		if (static::isNull($value)) {
			return '';
		}
		if (is_numeric($value)) {
			$timeStamp = ($value - 25569) * 24 * 3600;
			return date('Y-m-d', $timeStamp);
		}
		return $value;
	}
	
	/**
	 * 获取设置值
	 *
	 * @param $id
	 * @return array
	 */
	public static function getSettingValue($id): array
	{
		$settingInfo = query('reminder_settings', " id = '$id' ");
		if (!$settingInfo['id']) {
			return ['member' => [], 'threshold' => 0];
		} else {
			$member = explode(',', $settingInfo['member']);
			return ['member' => $member ?: [], 'threshold' => $settingInfo['threshold']];
		}
	}
	
	/**
	 * 筛选默认隐藏区域
	 *
	 * @param $html
	 * @return string
	 */
	public static function moreContainer($html): string
	{
		return "<span class='more-container'>" . $html . "</span><span class='more-btn' onclick='cutOut(this)'>展开</span>";
	}
	
	/** 相同参数的数量汇总处理
	 *
	 * @param array $para 参数数组
	 * @param array $num  数量数组
	 * @return array
	 */
	public static function dataArrangement(array $para, array $num): array
	{
		$arr = [];
		foreach ($para as $k => $v) {
			if (!array_key_exists($v, $arr)) {
				$arr[$v] = $num[$k];
			} else {
				$arr[$v] += $num[$k];
			}
		}
		return $arr;
	}
	
	/**
	 * 通过中台获取企业凭证
	 *
	 * @return void
	 */
	static function getCorpToken($app = 'erp', $isCache = true)
	{
		$corpid = para('qyWeChatCorpid');
		$key    = $corpid . 'accessToken';
		if ($isCache === true) {
			$accessToken = self::rGet($key);
			if ($accessToken) {
				return $accessToken;
			}
		}
		
		$res = static::request([
			'method'  => 'POST',
			'url'     => 'https://admin.saas.yumukeji.com/interface/v1/?cla=publicServer&fun=response&act=getCorpToken',
			'params'  => [
				'corpid' => $corpid,
				'app'    => $app,
			],
			'timeout' => 30,
		], true);
		if ($res['code'] == 0) {
			test('获取corpToken失败' . $res['warn']);
			return null;
		} else {
			self::rSet($key, $res['data']['corpToken'], $res['data']['expiresIn']);
			return $res['data']['corpToken'];
		}
	}
	
	/**
	 * 通过中台获取三方应用凭证
	 *
	 * @return void
	 */
	static function getSuiteToken($app = 'erp', $isCache = true)
	{
		if ($isCache === true) {
			$suiteAccessToken = self::rGet($app . 'suiteAccessToken');
			if ($suiteAccessToken) {
				return $suiteAccessToken;
			}
		}
		$res = static::request([
			'method'  => 'POST',
			'url'     => 'https://admin.saas.yumukeji.com/interface/v1/?cla=publicServer&fun=response&act=getSuiteToken',
			'params'  => [
				'app' => $app,
			],
			'timeout' => 30,
			'build'   => false,
		], true);
		if ($res['code'] == 0) {
			test('获取suiteToken失败' . $res['warn']);
			return null;
		} else {
			self::rSet($app . 'suiteAccessToken', $res['data']['suiteToken'], $res['data']['expiresIn']);
			return $res['data']['suiteToken'];
		}
	}
}

include_once "base.php";
